package com.example.origin.finance.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.HashUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.origin.finance.dto.JijinDto;
import com.example.origin.finance.dto.XueqiuKline;
import com.example.origin.finance.dto.XueqiuQuote;
import com.example.origin.finance.enums.TableNameEnum;
import com.example.origin.finance.service.impl.*;
import com.example.origin.finance.utils.CalcUtil;
import com.example.origin.resttemplate.util.RestUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@Slf4j
@RequestMapping("store")
@RestController
public class StoreController {
    @Resource
    private XueqiuAStockCodeService xueqiuAStockCodeService;

    @Resource
    private XueqiuUsStockCodeService xueqiuUsStockCodeService;
    @Resource
    private XueqiuQuoteStoreService xueqiuQuoteStoreService;

    @Resource
    private XueqiuKlineMultiStoreService xueqiuKlineMultiStoreService;

    @Resource
    private XueqiuIndicatorMultiStoreService xueqiuIndicatorMultiStoreService;

    @Resource
    private XueqiuCashFlowMultiStoreService xueqiuCashFlowMultiStoreService;

    @Resource
    private CnInfoReportMultiStoreService cnInfoReportMultiStoreService;
    @Resource
    private RestTemplate restTemplate;
    @Resource(name = "defaultRedisTemplate")
    private RedisTemplate<String, Set<String>> stringSetRedisTemplate;

    @Resource
    private MongoTemplate mongoTemplate;


    private static final int expire = 30;

    static final Integer klineTableNum = 10;
    private static final String setkeyJiJin = "jijin";
    private static final String xueqiuQuoteTableJiJin = "xueqiu_quote_jijin";
    private static final String xueqiuKlineTableJiJin = "xueqiu_kline_jijin";

    @SneakyThrows
//    @GetMapping("initDataJiJin")
    public String initDataJiJin(@RequestParam(required = false) String codeOption) {

        Set<String> list = new HashSet<>();
        if (ObjectUtil.isNotEmpty(codeOption)) {
            Query query = new Query();
            query.addCriteria(Criteria.where("code").is(codeOption));
            if (mongoTemplate.collectionExists(xueqiuQuoteTableJiJin)) {
                mongoTemplate.remove(query, xueqiuQuoteTableJiJin);
            }
            String klineTable = klineTable(xueqiuKlineTableJiJin, HashUtil.rotatingHash(codeOption, 10));
            if (mongoTemplate.collectionExists(klineTable)) {
                mongoTemplate.remove(query, klineTable);
            }
            list.add(codeOption);
        } else {
            list = codes(setkeyJiJin, set -> {
                JSONObject jsonObject = jijin();
                for (String key : jsonObject.keySet()) {
                    JijinDto jijinDto = jsonObject.get(key, JijinDto.class);
                    String code = jijinDto.getCode();
                    if (code.startsWith("1") || code.startsWith("5")) {
                        set.add(code);
                    }
                }
            });
        }

        List<String> codes = new ArrayList<>(list);
        xueqiuQuoteTable(codes, xueqiuQuoteTableJiJin);
        xueqiuKlineTable(codes, xueqiuKlineTableJiJin);
        return "end";
    }


    @SneakyThrows
    @GetMapping("initDataGuPiao")
    public String initDataGuPiao(@RequestParam(required = false) String codeOption, @RequestParam(defaultValue = "5") Integer num) {
        List<String> codes = xueqiuAStockCodeService.listCode(codeOption);

        TableNameEnum tableNameEnum = TableNameEnum.get(num);
        switch (Objects.requireNonNull(tableNameEnum)) {
            case XUEQIU_GUPIAO_QUOTE:
                xueqiuQuoteStoreService.storeTable(codes, TableNameEnum.XUEQIU_GUPIAO_QUOTE);
                break;
            case CNINFO_GUPIAO_REPORT:
                cnInfoReportMultiStoreService.storeTable(codes, TableNameEnum.CNINFO_GUPIAO_REPORT);
                break;
            case XUEQIU_GUPIAO_KLINE:
                xueqiuKlineMultiStoreService.storeTable(codes, TableNameEnum.XUEQIU_GUPIAO_KLINE);
                break;
            case XUEQIU_GUPIAO_INDICATOR:
                xueqiuIndicatorMultiStoreService.storeTable(codes, TableNameEnum.XUEQIU_GUPIAO_INDICATOR);
                break;
            case XUEQIU_GUPIAO_CASH_FLOW:
                xueqiuCashFlowMultiStoreService.storeTable(codes, TableNameEnum.XUEQIU_GUPIAO_CASH_FLOW);
                break;
            default:
                break;
        }
        return "end";
    }


    @SneakyThrows
    @GetMapping("initDataGuPiaoUS")
    public String initDataGuPiaoUS(@RequestParam(required = false) String codeOption, @RequestParam(defaultValue = "7") Integer num) {
        List<String> codes = xueqiuUsStockCodeService.listCode(codeOption);

        TableNameEnum tableNameEnum = TableNameEnum.get(num);
        switch (Objects.requireNonNull(tableNameEnum)) {
            case XUEQIU_GUPIAO_QUOTE_US:
                xueqiuQuoteStoreService.storeTable(codes, TableNameEnum.XUEQIU_GUPIAO_QUOTE_US);
                break;
            case XUEQIU_GUPIAO_KLINE_US:
                xueqiuKlineMultiStoreService.storeTable(codes, TableNameEnum.XUEQIU_GUPIAO_KLINE_US);
                break;
            default:
                break;
        }
        return "end";
    }

    @SneakyThrows
    @GetMapping("initDataJiJinUS")
    public String initDataJiJinUS(@RequestParam(required = false,defaultValue = "TQQQ") String codeOption, @RequestParam(defaultValue = "9") Integer num) {
        List<String> codes = Collections.singletonList(codeOption);
        TableNameEnum tableNameEnum = TableNameEnum.get(num);
        switch (Objects.requireNonNull(tableNameEnum)) {
            case XUEQIU_JIJIN_KLINE_US:
                xueqiuKlineMultiStoreService.storeTable(codes, TableNameEnum.XUEQIU_JIJIN_KLINE_US);
                break;
            case XUEQIU_JIJIN_QUOTE_US:
                xueqiuQuoteStoreService.storeTable(codes, TableNameEnum.XUEQIU_JIJIN_QUOTE_US);
                break;
            default:
                break;
        }
        return "end";
    }




    private Set<String> codes(String setkey, Consumer<Set<String>> consumer) {
        Set<String> stringSet = stringSetRedisTemplate.opsForValue().get(setkey);
        if (ObjectUtil.isEmpty(stringSet)) {
            stringSet = new HashSet<>();
            consumer.accept(stringSet);
            stringSetRedisTemplate.opsForValue().set(setkey, stringSet, expire, TimeUnit.DAYS);
        }

        return stringSet;
    }

    private void xueqiuQuoteTable(List<String> list, String xueqiuQuoteTable) {
        boolean flag = list.size() == 1;

        if (!flag) {
            mongoTemplate.dropCollection(xueqiuQuoteTable);
        }

        if (flag || !mongoTemplate.collectionExists(xueqiuQuoteTable)) {
            List<XueqiuQuote> quotes = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                String code = list.get(i);
                XueqiuQuote xueqiuQuote = xueqiuQuote(code);
                if (xueqiuQuote != null && !ObjectUtil.isEmpty(xueqiuQuote.getCode())) {
                    quotes.add(xueqiuQuote);
                    BigDecimal decimal = CalcUtil.multiply(CalcUtil.divide(i, list.size()), 100).setScale(2, RoundingMode.HALF_UP);
                    log.info("xueqiuQuoteTable process：{} %", decimal);
                }
            }
            if (flag && mongoTemplate.collectionExists(xueqiuQuoteTable)) {
                mongoTemplate.remove(Query.query(Criteria.where("code").is(quotes.get(0).getCode())), xueqiuQuoteTable);
            }
            mongoTemplate.insert(quotes, xueqiuQuoteTable);
            if (!flag) {
                createIndex(xueqiuQuoteTable);
            }
        }
    }

    @SneakyThrows
    private void xueqiuKlineTable(List<String> list, String xueqiuTable) {
        String table = klineTable(xueqiuTable, 0);
        boolean flag = list.size() == 1;
        if (!flag) {
            mongoTemplate.dropCollection(table);
        }

        if (flag || !mongoTemplate.collectionExists(table)) {
            MultiValueMap<String, XueqiuKline> linkedMultiValueMap = new LinkedMultiValueMap<>();
            for (int i = 0; i < list.size(); i++) {
                String code = list.get(i);
                fillKlineMap(code, linkedMultiValueMap, -720);
                BigDecimal decimal = CalcUtil.multiply(CalcUtil.divide(i, list.size()), 100).setScale(2, RoundingMode.HALF_UP);
                log.info("process：{} %", decimal);
            }
            MultiValueMap<String, XueqiuKline> klineMultiValueMap = klineMap(linkedMultiValueMap, xueqiuTable);
            if (klineMultiValueMap.isEmpty()) {
                return;
            }
            CountDownLatch countDownLatch = new CountDownLatch(klineMultiValueMap.size());
            ExecutorService executorService = Executors.newFixedThreadPool(100);
            for (Map.Entry<String, List<XueqiuKline>> listEntry : klineMultiValueMap.entrySet()) {
                executorService.execute(() -> {
                    String tableName = listEntry.getKey();
                    List<XueqiuKline> value = listEntry.getValue();
                    if (CollectionUtils.isEmpty(value)) {
                        return;
                    }
                    log.info("start >> tableName：{}，count：{}", tableName, value.size());
                    if (!flag) {
                        mongoTemplate.dropCollection(tableName);
                    }
                    if (flag && mongoTemplate.collectionExists(tableName)) {
                        mongoTemplate.remove(Query.query(Criteria.where("code").is(value.get(0).getCode())), tableName);
                    }
                    mongoTemplate.insert(value, tableName);
                    if (!flag) {
                        createIndex(tableName);
                    }
                    countDownLatch.countDown();
                    log.info("end >> tableName：{}，count：{}", tableName, value.size());
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            boolean b;
            do {
                b = executorService.awaitTermination(1, TimeUnit.SECONDS);
            } while (!b);
        }
    }


    private static MultiValueMap<String, XueqiuKline> klineMap(MultiValueMap<String, XueqiuKline> klineMultiValueMap, String xueqiuKlineTable) {
        Set<String> codes = klineMultiValueMap.keySet();

        MultiValueMap<Integer, String> multiValueMap = new LinkedMultiValueMap<>();
        for (String code : codes) {
            multiValueMap.add(HashUtil.rotatingHash(code, klineTableNum), code);
        }
        MultiValueMap<String, XueqiuKline> linkedMultiValueMap = new LinkedMultiValueMap<>();
        for (int i = 0; i < klineTableNum; i++) {
            if (multiValueMap.containsKey(i)) {
                for (String s : Objects.requireNonNull(multiValueMap.get(i))) {
                    List<XueqiuKline> klineList = klineMultiValueMap.get(s);
                    if (!ObjectUtil.isEmpty(klineList)) {
                        linkedMultiValueMap.addAll(klineTable(xueqiuKlineTable, i), Objects.requireNonNull(klineList));
                    }
                }
            }
        }
        return linkedMultiValueMap;
    }

    private static String klineTable(String xueqiuKlineTable, int i) {
        return xueqiuKlineTable + "_" + i;
    }

    private void fillKlineMap(String code, MultiValueMap<String, XueqiuKline> x13, Integer count) {
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("begin", System.currentTimeMillis() + "");
        paramMap.put("period", "day");
        paramMap.put("type", "before");
        paramMap.put("count", "" + count);
        paramMap.put("indicator", "kline,pe,pb,ps,pcf,market_capital,agt,ggt,balance");

        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("Cookie", getXueqiuCookie());
        String u1 = "https://stock.xueqiu.com/v5/stock/chart/kline.json";


        if (!setParamMap(code, paramMap)) {
            return;
        }
        ResponseEntity<Object> response = RestUtil.restGet(restTemplate, u1, paramMap, headerMap, ParameterizedTypeReference.forType(Map.class));

        JSONObject x1 = JSONUtil.parseObj(response);

        JSONObject x2 = x1.getJSONObject("body").getJSONObject("data");
        JSONArray x3 = x2.getJSONArray("item");
        JSONArray x4 = x2.getJSONArray("column");
        String x5 = x2.get("symbol", String.class);

        if (x4 != null && x3 != null && !x4.isEmpty() && !x3.isEmpty()) {

            Map<Integer, String> x10 = new HashMap<>();
            for (int i = 0; i < x4.size(); i++) {
                x10.put(i, x4.getStr(i));
            }

            List<JSONArray> x8 = x3.toList(JSONArray.class);
            for (int i = 0; i < x8.size(); i++) {
                JSONArray x9 = x8.get(i);
                Map<String, Object> x11 = new HashMap<>();
                for (int j = 0; j < x9.size(); j++) {
                    Object o = x9.get(j);
                    String s = x10.get(j);
                    x11.put(s, o);
                }
                XueqiuKline x12 = BeanUtil.mapToBean(x11, XueqiuKline.class, false, CopyOptions.create());
                x12.setCode(x5.substring(2));
                x12.setSymbol(x5);
                x13.add(x12.getCode(), x12);
            }
        }
    }

    private XueqiuQuote xueqiuQuote(String code) {
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("extend", "detail");
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("Cookie", getXueqiuCookie());
        String u1 = "https://stock.xueqiu.com/v5/stock/quote.json";

        if (!setParamMap(code, paramMap)) {
            return null;
        }
        ResponseEntity<Object> response = RestUtil.restGet(restTemplate, u1, paramMap, headerMap, ParameterizedTypeReference.forType(Map.class));
        XueqiuQuote x2 = JSONUtil.parseObj(response).getJSONObject("body").getJSONObject("data").get("quote", XueqiuQuote.class);
        return x2;
    }

    private static boolean setParamMap(String code, Map<String, String> paramMap) {
        if (code.startsWith("1") || code.startsWith("0") || code.startsWith("3") || code.startsWith("2")) {
            paramMap.put("symbol", "SZ" + code);
            return true;
        }
        if (code.startsWith("5") || code.startsWith("6") || code.startsWith("9")) {
            paramMap.put("symbol", "SH" + code);
            return true;
        }
        return false;
    }

    private void createIndex(String table) {
        Index index = new Index();
        index.on("code", Sort.Direction.ASC);
        mongoTemplate.indexOps(table).ensureIndex(index);
    }

    private JSONObject jijin() {
        JSONObject etf = etf();
        etf.putAll(zsx());
        return etf;
    }


    private JSONObject etf() {
        String url = "https://fund.10jqka.com.cn/data/Net/info/ETF_F009_desc_0_0_1_9999_0_0_0_jsonp_g.html";
        ResponseEntity<String> response = RestUtil.restGet(restTemplate, url, null, null, ParameterizedTypeReference.forType(String.class));
        JSONObject v1 = JSONUtil.parseObj(response);
        String v2 = v1.get("body", String.class);

        JSONObject v3 = JSONUtil.parseObj(v2.substring(2, v2.length() - 1));


        JSONObject v4 = v3.getJSONObject("data").getJSONObject("data");
        return v4;
    }

    private JSONObject zsx() {
        String url = "https://fund.10jqka.com.cn/data/Net/info/zsx_F009_desc_0_0_1_9999_0_0_0_jsonp_g.html";
        ResponseEntity<String> response = RestUtil.restGet(restTemplate, url, null, null, ParameterizedTypeReference.forType(String.class));
        JSONObject v1 = JSONUtil.parseObj(response);
        String v2 = v1.get("body", String.class);

        JSONObject v3 = JSONUtil.parseObj(v2.substring(2, v2.length() - 1));


        JSONObject v4 = v3.getJSONObject("data").getJSONObject("data");
        return v4;
    }

    public String getXueqiuCookie() {
        Object xueqiuCookie = RequestContextHolder.currentRequestAttributes().getAttribute("xueqiuCookie", RequestAttributes.SCOPE_REQUEST);
        return Objects.requireNonNull(xueqiuCookie).toString();
    }


}
